﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows;
using System.Diagnostics;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using System.Diagnostics.CodeAnalysis;

namespace Jbe.Composite.Extensions
{
    /// <summary>
    /// Abstract base class for the PresentationModel pattern.
    /// </summary>
    public abstract class PresentationModel : Model, IWeakEventListener
    {
        private readonly IView view;


        /// <summary>
        /// Initializes a new instance of the <see cref="PresentationModel&lt;TView&gt;"/> class and
        /// attaches itself as <c>DataContext</c> to the view.
        /// </summary>
        /// <param name="view">The view.</param>
        protected PresentationModel(IView view)
            : this(view, false)
        { }


        /// <summary>
        /// Initializes a new instance of the <see cref="PresentationModel&lt;TView&gt;"/> class.
        /// </summary>
        /// <param name="view">The view.</param>
        /// <param name="isChild">if set to <c>true</c> then the PresentationModel is a child of another PresentationModel.</param>
        protected PresentationModel(IView view, bool isChild)
        {
            if (view == null) { throw new ArgumentNullException("view"); }
            this.view = view;
            if (!isChild)
            {
                // Set DataContext of the view has to be delayed so that the PresentationModel can initialize the internal data (e.g. Commands)
                // before the view starts with DataBinding.
                Dispatcher.CurrentDispatcher.BeginInvoke(new Action<IView, PresentationModel>(SetDataContext), View, this);
            }
        }

        private static void SetDataContext(IView view, PresentationModel presentationModel)
        {
            view.DataContext = presentationModel;
        }


        /// <summary>
        /// Gets the associated view.
        /// </summary>
        /// <value>The view associated with the PresentationModel.</value>
        public IView View { get { return view; } }


        /// <summary>
        /// Receives events from the centralized event manager.
        /// </summary>
        /// <param name="managerType">The type of the <see cref="T:System.Windows.WeakEventManager"/> calling this method.</param>
        /// <param name="sender">Object that originated the event.</param>
        /// <param name="e">Event data.</param>
        /// <returns>
        /// true if the listener handled the event. It is considered an error by the <see cref="T:System.Windows.WeakEventManager"/> handling in WPF to register a listener for an event that the listener does not handle. Regardless, the method should return false if it receives an event that it does not recognize or handle.
        /// </returns>
        bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            bool handled = false;

            ReceiveWeakEvent(managerType, sender, e, ref handled);

            if (!handled) { throw new InvalidOperationException("The weak event must be handled by someone."); }

            return handled;
        }

        /// <summary>
        /// Override this method to handle weak events.
        /// </summary>
        /// <param name="managerType">Type of the weak event manager that handles this event.</param>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        /// <param name="handled">Set this parameter to <c>true</c> when you handle this event.</param>
        [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "3#")]
        protected virtual void ReceiveWeakEvent(Type managerType, object sender, EventArgs e, ref bool handled)
        {
        }
    }


    /// <summary>
    /// Derive from this class if you implement the PresentationModel pattern.
    /// </summary>
    /// <typeparam name="TView">The type of the view. Do provide an interface as type and not the concrete type itself.</typeparam>
    public abstract class PresentationModel<TView> : PresentationModel where TView : IView
    {
        private readonly TView view;


        /// <summary>
        /// Initializes a new instance of the <see cref="PresentationModel&lt;TView&gt;"/> class and
        /// attaches itself as <c>DataContext</c> to the view.
        /// </summary>
        /// <param name="view">The view.</param>
        protected PresentationModel(TView view)
            : this(view, false)
        { }


        /// <summary>
        /// Initializes a new instance of the <see cref="PresentationModel&lt;TView&gt;"/> class.
        /// </summary>
        /// <param name="view">The view.</param>
        /// <param name="isChild">if set to <c>true</c> then the PresentationModel is a child of another PresentationModel.</param>
        protected PresentationModel(TView view, bool isChild)
            : base(view, isChild)
        {
            this.view = view;
        }


        /// <summary>
        /// Gets the associated view.
        /// </summary>
        /// <value>The view associated with the PresentationModel.</value>
        public new TView View { get { return view; } }
    }
}
